package de.mmis.core.base.abstracttree.serializer.selector;

import java.util.ArrayList;
import java.util.Map;

import de.mmis.core.base.DeserializableAnnotations.ObjectDeserializable;
import de.mmis.core.base.Position;
import de.mmis.core.base.abstracttree.InnerNode;
import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.abstracttree.serializer.DeserializableException;
import de.mmis.core.base.abstracttree.serializer.Serializable;
import de.mmis.core.base.abstracttree.serializer.internal.ObjectSerializer;
import de.mmis.core.base.abstracttree.serializer.internal.PositionSerializer;
import de.mmis.core.base.abstracttree.serializer.internal.StackTraceElementSerializer;
import de.mmis.core.base.gclass.GArray;
import de.mmis.core.base.gclass.GClass;
import de.mmis.core.base.gclass.GParameterized;

/**
 * generic {@link InstanceSelector} choosing between different structured types,
 * like arrays, lists, maps {@link Tree}s
 * 
 * @author Christian Koch
 */
public class GenericStructSelector extends InstanceSelector {

	private final InnerNode struct;
	private final ClassLoader classLoader;

	public GenericStructSelector(InnerNode struct, ClassLoader classLoader,
			String id, Map<String, Object> idmap) {
		super(id, idmap);

		this.struct = struct;
		this.classLoader = classLoader;
	}

	@Override
	public Object createInstance(GClass gclass)
			throws InstanceSelectorException {
		if (gclass instanceof GArray || gclass.getBaseClass().isArray()) {
			return new ArraySelector(this.struct, this.classLoader, getID(),
					getIDMap()).createInstance(gclass);
		}
		Class<?> base = gclass.getBaseClass();

		if (base.getAnnotation(ObjectDeserializable.class) != null) {
			try {
				Tree[] exps = new Tree[this.struct.getNumberOfSubTrees()];
				for (int i = 0; i < exps.length; i++)
					exps[i] = this.struct.getSubTree(i);
				return ObjectSerializer.deserialize(exps, this.classLoader,
						getID(), getIDMap()).createInstance(gclass);
			} catch (DeserializableException e) {
				throw new InstanceSelectorException("item \"" + this.struct
						+ "\" cannot be converted to type \"" + base.getName()
						+ "\"", e);
			}
		}

		if (Tree.class.isAssignableFrom(base)) {
			return new SerializableSelector(new Tree[] { this.struct },
					this.classLoader, getID(), getIDMap())
					.createInstance(gclass);
		}

		if (Serializable.class.isAssignableFrom(base)) {
			Tree[] exps = new Tree[this.struct.getNumberOfSubTrees()];
			for (int i = 0; i < exps.length; i++)
				exps[i] = this.struct.getSubTree(i);

			return new SerializableSelector(exps, this.classLoader, getID(),
					getIDMap()).createInstance(gclass);
		}

		if (Map.class.isAssignableFrom(base))
			return new MapSelector(this.struct, this.classLoader, getID(),
					getIDMap()).createInstance(gclass);

		if (Iterable.class.isAssignableFrom(base))
			return new ListSelector(this.struct, this.classLoader, getID(),
					getIDMap()).createInstance(gclass);

		if (base == Position.class) {
			try {
				return PositionSerializer.deserializePosition(this.struct);
			} catch (DeserializableException e) {
				throw new InstanceSelectorException(
						"cannot convert generic struct " + this.struct
								+ " to type \"" + gclass + "\"", e);
			}
		}

		if (base == StackTraceElement.class) {
			try {
				return StackTraceElementSerializer
						.deserializeStackTraceElement(this.struct);
			} catch (DeserializableException e) {
				throw new InstanceSelectorException(
						"cannot convert generic struct " + this.struct
								+ " to type \"" + gclass + "\"", e);
			}
		}

		if (base == Object.class) {
			return createInstance(new GParameterized(ArrayList.class,
					new GClass[] { GClass.fromClass(Object.class) }));
		}

		throw new InstanceSelectorException("cannot convert generic struct "
				+ this.struct + " to type \"" + gclass + "\"");
	}
}
